﻿using System;
using System.Collections.Generic;

using ET;

namespace Marx
{
    public class DirtyQueue<T> where T : DBModel
    {
        private class Wrap
        {
            public bool IsDirty => Model.IsDirty;

            public string Key;
            public long Time;
            public string Collection;
            public T Model;

            public ETTask Save(DBComponent db)
            {
                return Model.Save(db, Collection);
            }
        }

        private HashSet<string> keys_ = new HashSet<string>();
        private Queue<Wrap> saves_ = new Queue<Wrap>();
        private DBComponent db_;

        private long saveInterval_;

        public DirtyQueue(DBComponent db, long interval)
        {
            db_ = db;
            saveInterval_ = interval;
        }

        public void Update()
        {
            while (saves_.Count > 0)
            {
                var wrap = saves_.Peek();
                if (TimeHelper.ClientNow() >= wrap.Time)
                {
                    wrap = saves_.Dequeue();
                    keys_.Remove(wrap.Key);
                    if (wrap.IsDirty)
                    {
                        wrap.Save(db_).Coroutine();
                    }
                }
                else
                {
                    break;
                }
            }
        }

        public ETTask Save(T model, string collection)
        {
            return model.Save(db_, collection);
        }

        public void Push(T model, string collection)
        {
            Push(model, collection, saveInterval_);
        }

        public void Push(T model, string collection, long delay)
        {
            var key = collection + model.Id;
            Push(key, model, collection, delay);
        }

        public void Push(string key, T model, string collection)
        {
            Push(key, model, collection, saveInterval_);
        }

        public void Push(string key, T model, string collection, long delay)
        {
            model.SetDirty();
            if (keys_.Contains(key))
            {
                return;
            }

            keys_.Add(key);
            var wrap = new Wrap()
            {
                Key = key,
                Collection = collection,
                Time = TimeHelper.ClientNow() + delay,
                Model = model,
            };
            saves_.Enqueue(wrap);
        }

        public void Clear()
        {
            keys_.Clear();
            saves_.Clear();
        }

        public async ETTask SaveAndClear()
        {
            foreach (var wrap in saves_)
            {
                await wrap.Save(db_);
            }
            Clear();
        }
    }
}
